/*
** $Id: ldo.c,v 2.37 2005/12/22 16:19:56 roberto Exp roberto $
** Stack and Call structure of Lua
** See Copyright Notice in lua.h
*/


#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
// #include <stdio.h>

#define ldo_c
#define LUA_CORE

#include "lua.h"

#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
#include "lmem.h"
#include "lobject.h"
#include "lopcodes.h"
#include "lparser.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include "ltm.h"
#include "lundump.h"
#include "lvm.h"
#include "lzio.h"




/*
** {======================================================
** Error-recovery functions
** =======================================================
*/


/* chain list of long jump buffers */
struct lua_longjmp {
  struct lua_longjmp *previous;
  luai_jmpbuf b;
  volatile int status;  /* error code */
};


void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
  switch (errcode) {
    case LUA_ERRMEM: {
      setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG));
      break;
    }
    case LUA_ERRERR: {
      setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
      break;
    }
    case LUA_ERRSYNTAX:
    case LUA_ERRRUN: {
      setobjs2s(L, oldtop, L->top - 1);  /* error message on current top */
      break;
    }
  }
  L->top = oldtop + 1;
}


static void restore_stack_limit (lua_State *L) {
  lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);
  if (L->size_ci > LUAI_MAXCALLS) {  /* there was an overflow? */
    int inuse = cast_int(L->ci - L->base_ci);
    if (inuse + 1 < LUAI_MAXCALLS)  /* can `undo' overflow? */
      luaD_reallocCI(L, LUAI_MAXCALLS);
  }
}


static void resetstack (lua_State *L, int status) {
  L->ci = L->base_ci;
  L->base = L->ci->base;
  luaF_close(L, L->base);  /* close eventual pending closures */
  luaD_seterrorobj(L, status, L->base);
  L->nCcalls = 0;
  L->allowhook = 1;
  restore_stack_limit(L);
  L->errfunc = 0;
  L->errorJmp = NULL;
}

// 虽然有些情形下，会不生成errMsg到栈顶然后调用luaD_throw，但这是极其罕见的情况，通常是在处理报错时报错得更离谱才会这样（全局搜了下源码）
void luaD_throw (lua_State *L, int errcode) {
  if (L->errorJmp) {
    // 设置错误码，并回跳到上一次的LUAI_TRY（setjump）调用点
    L->errorJmp->status = errcode;
    LUAI_THROW(L, L->errorJmp);
  }
  else {
    // 无errorJmp可以回跳，则这个异常无人承接处理，lua vm直接退出
    L->status = cast_byte(errcode);
    // 调用global_State的panic函数，通常用于给出lua vm终止的原因，默认为luaL_newstate()时注入的panic()函数
    if (G(L)->panic) {
      resetstack(L, errcode);
      lua_unlock(L);
      G(L)->panic(L);
    }

    // 终止进程，进程的相关资源被操作系统自动回收
    exit(EXIT_FAILURE);
  }
}

// 以保护模式运行函数f，其实现基于C语言的setjump longjump机制，最终返回此次函数调用的status（结果码），外部调用由返回值不为0就可以知道发生了异常，并承接处理
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
  // errorJmp切换为新构建的setjump点，旧的作为前缀
  struct lua_longjmp lj;
  lj.status = 0;
  lj.previous = L->errorJmp;  /* chain new error handler */
  L->errorJmp = &lj;

  // setjump()并执行函数f，如果函数执行过程中调用了LUAI_THROW()，则直接、立刻回到setjump()处，后续不执行(*f)(L, ud);
  LUAI_TRY(L, &lj,
    (*f)(L, ud);
  );

  // errorJmp切回旧的（前缀）
  L->errorJmp = lj.previous;  /* restore old error handler */
  return lj.status;
}

/* }====================================================== */


static void correctstack (lua_State *L, TValue *oldstack) {
  CallInfo *ci;
  GCObject *up;
  L->top = (L->top - oldstack) + L->stack;
  for (up = L->openupval; up != NULL; up = up->gch.next)
    gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack;
  for (ci = L->base_ci; ci <= L->ci; ci++) {
    ci->top = (ci->top - oldstack) + L->stack;
    ci->base = (ci->base - oldstack) + L->stack;
    ci->func = (ci->func - oldstack) + L->stack;
  }
  L->base = (L->base - oldstack) + L->stack;
}


void luaD_reallocstack (lua_State *L, int newsize) {
  TValue *oldstack = L->stack;
  int realsize = newsize + 1 + EXTRA_STACK;
  // 写成 (L->stack_last - L->stack + 1) == (L->stacksize - EXTRA_STACK) 可能更好理解一些
  lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);
  // 以当前L->stack的地址为基础，调用realloc()，将内存块扩张或缩小至realsize * sizeof(TValue)的大小
  luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue);
  L->stacksize = realsize;
  L->stack_last = L->stack + newsize;
  // 根据realloc()的机制，可能L->stack的地址后面没有足够的空间，那么就会另申请一块空间，将原内存块中的数据copy过去，然后释放原内存块
  // 这样一来当前struct lua_State实例的top、base指针，CallInfo中的各种指向虚拟栈的指针，其指向就是错误的了，需要调整
  correctstack(L, oldstack);
}


void luaD_reallocCI (lua_State *L, int newsize) {
  CallInfo *oldci = L->base_ci;
  luaM_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo);
  L->size_ci = newsize;
  L->ci = (L->ci - oldci) + L->base_ci;
  L->end_ci = L->base_ci + L->size_ci - 1;
}


void luaD_growstack (lua_State *L, int n) {
  if (n <= L->stacksize)  /* double size is enough? */
    luaD_reallocstack(L, 2*L->stacksize);
  else
    luaD_reallocstack(L, L->stacksize + n);
}


static CallInfo *growCI (lua_State *L) {
  if (L->size_ci > LUAI_MAXCALLS)  /* overflow while handling overflow? */
    luaD_throw(L, LUA_ERRERR);
  else {
    luaD_reallocCI(L, 2*L->size_ci);
    if (L->size_ci > LUAI_MAXCALLS)
      luaG_runerror(L, "stack overflow");
  }
  return ++L->ci;
}


void luaD_callhook (lua_State *L, int event, int line) {
  lua_Hook hook = L->hook;
  if (hook && L->allowhook) {
    ptrdiff_t top = savestack(L, L->top);
    ptrdiff_t ci_top = savestack(L, L->ci->top);
    lua_Debug ar;
    ar.event = event;
    ar.currentline = line;
    if (event == LUA_HOOKTAILRET)
      ar.i_ci = 0;  /* tail call; no debug information about it */
    else
      ar.i_ci = cast_int(L->ci - L->base_ci);
    luaD_checkstack(L, LUA_MINSTACK);  /* ensure minimum stack size */
    L->ci->top = L->top + LUA_MINSTACK;
    lua_assert(L->ci->top <= L->stack_last);
    L->allowhook = 0;  /* cannot call hooks inside a hook */
    lua_unlock(L);
    (*hook)(L, &ar);
    lua_lock(L);
    lua_assert(!L->allowhook);
    L->allowhook = 1;
    L->ci->top = restorestack(L, ci_top);
    L->top = restorestack(L, top);
  }
}


static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {
  int i;
  int nfixargs = p->numparams; // 函数定义中，形参表的具名形参个数（除开 ...）
  Table *htab = NULL;
  StkId base, fixed;

  // 调用实际函数时，实参个数 < 形参表的具名形参个数，用nil补充；注意actual所代表的实参总数也在增加
  for (; actual < nfixargs; ++actual)
    setnilvalue(L->top++);

// LUA_COMPAT_VARARG，兼容Lua 5.0的编译选项，不预定义这个选项会比较好，加了的话，本次函数调用会多出一个叫arg的局部变量
// 阅读源码时你可以把这块代码当作不存在
#if defined(LUA_COMPAT_VARARG)
  if (p->is_vararg & VARARG_NEEDSARG) { /* compat. with old-style vararg? */
    int nvar = actual - nfixargs;  /* number of extra arguments */
    lua_assert(p->is_vararg & VARARG_HASARG);
    luaC_checkGC(L);
    htab = luaH_new(L, nvar, 1);  /* create `arg' table */
    for (i=0; i<nvar; i++)  /* put extra arguments into `arg' table */
      setobj2n(L, luaH_setnum(L, htab, i+1), L->top - nvar + i);
    /* store counter in field `n' */
    setnvalue(luaH_setstr(L, htab, luaS_newliteral(L, "n")), cast_num(nvar));
  }
#endif

  // 此时L->top的下方依次为：本次函数调用的实参、函数本身

  /* move fixed parameters to final position */

  // 此时虚拟栈中的实参list已经经过一轮fix（补充实参），fixed必定指向func上方第1个槽位上，即第1个实参
  // base指向最后一个实参的后一个槽位
  fixed = L->top - actual;  /* first fixed argument */
  base = L->top;  /* final position of first argument */

  // nfixargs = 形参表的具名形参个数；也就是说，只移动具名形参的那部分，可变参数那部分不复制
  // 最后，可变参数那部分的实参，位置处于base指针的下方
  // 注意，这里的L->top也一直在随之上移
  for (i=0; i<nfixargs; i++) {
    setobjs2s(L, L->top++, fixed+i); // set obj1 = obj2
    setnilvalue(fixed+i);
  }

  // 我画个图来表示pcall开始执行前的虚拟栈布局
  /*
    | 函数  |    实参（形参表具名部分）    |实参（可变参数部分）|
    |      |                             |                  |                        
    | func | 444 | 555 | 453 | nil | nil | 22 | nil | 33    |
  */

  // 目前的虚拟栈内存布局，左边靠近虚拟栈栈底，右边靠近栈顶
  /*
    | 函数 |    实参（形参表具名部分）     |实参（可变参数部分）| 被拷贝过来的实参（形参表具名部分）|
    |      |                             |                  |                        
    | func | nil | nil | nil | nil | nil | 22 | nil | 33    | 444 | 555 | 453 | nil | nil |
              ^                                                ^                            ^
              |                                                |                            | 
            fixed                                       L->base = base                    L->top
  */

  // 把实参重新复制到更上层的虚拟栈位置上是要干撒子？把原先位置上的元素置nil又是要干撒子？为什么要这样子做呢？
  // 我知道你脑子里充满了疑惑，但我肯定地告诉你，你的理解是没有错的，在lvm.c的case OP_VARARG处有写后续的取值逻辑

  /* add `arg' parameter */
  if (htab) {
    sethvalue(L, L->top++, htab);
    lua_assert(iswhite(obj2gco(htab)));
  }

  return base;
}


static StkId tryfuncTM (lua_State *L, StkId func) {
  const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL);
  StkId p;
  ptrdiff_t funcr = savestack(L, func);
  if (!ttisfunction(tm))
    luaG_typeerror(L, func, "call");
  /* Open a hole inside the stack at `func' */
  for (p = L->top; p > func; p--) setobjs2s(L, p, p-1);
  incr_top(L);
  func = restorestack(L, funcr);  /* previous call may change stack */
  setobj2s(L, func, tm);  /* tag method is the new function to be called */
  return func;
}



#define inc_ci(L) \
  ((L->ci == L->end_ci) ? growCI(L) : \
   (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))


int luaD_precall (lua_State *L, StkId func, int nresults) {
  LClosure *cl;
  ptrdiff_t funcr;

  // 如果不是一个函数数值，func可能是个设置了__call元方法的table，尝试找它的__call元方法执行
  if (!ttisfunction(func)) /* `func' is not a function? */
    func = tryfuncTM(L, func);  /* check the `function' tag method */

  funcr = savestack(L, func); // StkId func到虚拟栈栈底的偏移量
  cl = &clvalue(func)->l; // StkId -> TValue -> LClosure l, func不一定是LClosure，用指针引用一下而已
  L->ci->savedpc = L->savedpc; // 初始，lua_State的savedpc为nil

  if (!cl->isC) {  /* Lua function? prepare its call */
    CallInfo *ci;
    StkId st, base;
    Proto *p = cl->p; // 取函数定义中的原型数据

    // 检查并调整虚拟栈的大小，函数调用时会有一些临时变量需要存储到虚拟栈上，p->maxstacksize表示该函数原型在执行时，最多需要xx个虚拟栈槽位
    // 后续看编译部分的时候注意一下这个maxstacksize的数值是如何被确定的，它是否会考虑本chunk内调用其他函数的情况？
    // 我觉得是不会的，实际验证也是如此，如果在本层chunk中调用了其他函数，那么它不会考虑被调函数的内部情况，也不会考虑被调函数的返回值有多少个
    // lua的策略是，在真正要执行函数的时候再调整lua_State虚拟栈的大小，见OP_CALL指令执行时会调用luaD_precall()；
    // 然后在gc流程中会检查lua_State虚拟栈是否太大，太大的话就释放缩小
    luaD_checkstack(L, p->maxstacksize);
    // printf("luaD_precall show %s-%d %d\n", getstr(p->source), p->linedefined, p->maxstacksize);

    // 用偏移量取回StkId func，上面的luaD_checkstack()可能会将虚拟栈迁移到另一块内存中，所以这里得用偏移量取出（恍然大悟）
    func = restorestack(L, funcr);

    if (!p->is_vararg) {  /* no varargs? 函数定义的参数表没有可变参数 ... */
      // 调整L->top的指向，不能超过 (StkId func + 1 + 原型Proto形参表的参数个数)，也就是说，多余的实参会在这一步被舍弃掉
      base = func + 1;
      if (L->top > base + p->numparams)
        L->top = base + p->numparams;
    }
    else {  /* vararg function */
      // Proto原型的形参表有可变参数 ... ，那么本次执行的函数调用栈，需要接受外部调用传入的所有实参；
      // nargs为本次函数调用中，实参的总数
      int nargs = cast_int(L->top - func) - 1;
      // base 的指向看 adjust_varargs()的内部注释
      base = adjust_varargs(L, p, nargs);
      // adjust_varargs() -> luaC_checkGC()，虚拟栈可能会迁移内存，这里用偏移量重新取回回来
      func = restorestack(L, funcr);  /* previous call may change the stack */
    }

    // return ++L->ci，递进到next CallInfo，供此次函数调用使用，如果CallInfo实例不够用，会自动扩容
    ci = inc_ci(L);  /* now `enter' new function */
  
    // 对新CallInfo实例各种赋值，顺便切一下L->base、L->savedpc的数值
    ci->func = func;
    L->base = ci->base = base;
    ci->top = L->base + p->maxstacksize;
    lua_assert(ci->top <= L->stack_last);
    L->savedpc = p->code;  /* starting point */
    ci->tailcalls = 0;
    ci->nresults = nresults;

    // 已经属于新CallInfo的这段虚拟栈重置为nil
    for (st = L->top; st < ci->top; st++)
      setnilvalue(st);

    // 维护L->top
    L->top = ci->top;

    // lua hook机制的相关逻辑，可以先不看
    if (L->hookmask & LUA_MASKCALL) {
      L->savedpc++;  /* hooks assume 'pc' is already incremented */
      luaD_callhook(L, LUA_HOOKCALL, -1);
      L->savedpc--;  /* correct 'pc' */
    }

    return PCRLUA;
  }
  else {  /* if is a C function, call it */
    CallInfo *ci;
    int n;

    // 执行CClosure前，确保剩余可用的栈位至少有LUA_MINSTACK个
    luaD_checkstack(L, LUA_MINSTACK);  /* ensure minimum stack size */

    ci = inc_ci(L);  /* now `enter' new function */

    // 调整新CallInfo实例的信息
    ci->func = restorestack(L, funcr); // 根据偏移量重新找回要执行的func所在的栈位
    L->base = ci->base = ci->func + 1;
    ci->top = L->top + LUA_MINSTACK; // 设置C函数调用栈的栈顶，实际执行时不够的话，C函数的定义中可以用luaL_checkstack()接口增长函数调用栈
    lua_assert(ci->top <= L->stack_last);
    ci->nresults = nresults;

    // 注意，这里未更改L->top = 的指向，因为C函数目前还无法确定最终要使用的虚拟栈栈位有多少个
    // 所以采取的策略是在C函数执行时，再将L->top逐渐上移，api_incr_top()

    // lua hook机制相关
    if (L->hookmask & LUA_MASKCALL)
      luaD_callhook(L, LUA_HOOKCALL, -1);
    lua_unlock(L);

    // 以C函数指针获取到函数地址，然后调用执行它
    // 从后面的源码中不难看出，C接口最后return的数值，其含义如下：
    // n < 0 表示将lua协程挂起了；n >= 0 表示把n个返回值留在了虚拟栈中，交由lua层处理；这也是我们自定C接口时所需要遵循的规定
    n = (*curr_func(L)->c.f)(L);  /* do the actual call */
    lua_lock(L);

    if (n < 0)  /* yielding? */
      return PCRYIELD;
    else {
      // C接口正常执行完毕，走正常的收尾流程，往虚拟栈里放了一些返回值，第一个返回值所在的槽位 = L->top - n
      luaD_poscall(L, L->top - n);
      return PCRC;
    }
  }
}


static StkId callrethooks (lua_State *L, StkId firstResult) {
  ptrdiff_t fr = savestack(L, firstResult);  /* next call may change stack */
  luaD_callhook(L, LUA_HOOKRET, -1);
  if (f_isLua(L->ci)) {  /* Lua function? */
    while (L->ci->tailcalls--)  /* call hook for eventual tail calls */
      luaD_callhook(L, LUA_HOOKTAILRET, -1);
  }
  return restorestack(L, fr);
}

// 将本次函数调用的返回值入栈，并将L->ci恢复到上一层函数调用的状态
// firstResult指向第一个返回值所在的虚拟栈槽位
int luaD_poscall (lua_State *L, StkId firstResult) {
  StkId res;
  int wanted, i;
  CallInfo *ci;

  // 如果设置了lua hook，在此处调用
  if (L->hookmask & LUA_MASKRET)
    firstResult = callrethooks(L, firstResult);

  ci = L->ci--; // lua_State的ci切回上一个CallInfo，局部的ci仍为当前函数调用的CallInfo

  // 函数调用结束return时，返回值的赋值从被调func所在的槽位开始
  res = ci->func;  /* res == final position of 1st result */
  wanted = ci->nresults; // 本次函数调用，期望有多少个返回值，其实是 调用方 决定是多少的
  // 如果期待有返回值，那么返回值必定会拿去干某些事情，说明函数调用的前面有东西在承接着返回值

  // lua_State的两个成员变量恢复到上一个CallInfo的记录状态
  L->base = (ci - 1)->base;  /* restore base */
  L->savedpc = (ci - 1)->savedpc;  /* restore savedpc */

  /* move results to correct place */
  // 此时函数调用栈中，自底向上依次为 被调func、若干个实参或过程值（可能没有）、返回值1、返回值2、...
  // 现在将返回值拷贝到自StkId func开始的槽位上，其实就是整体向下挪了X个槽位
  for (i = wanted; i != 0 && firstResult < L->top; i--)
    setobjs2s(L, res++, firstResult++);

  // 若实际return的返回值数量不满足nresults的要求，则用nil补全
  while (i-- > 0)
    setnilvalue(res++);

  L->top = res; // 移动L->top指向这堆返回值的上方槽位

  return (wanted - LUA_MULTRET);  /* return 0 if nresults == LUA_MULTRET; else return (nresults + 1), return > 0 */
}


/*
** Call a function (C or Lua). The function to be called is at *func.
** The arguments are on the stack, right after the function.
** When returns, all the results are on the stack, starting at the original
** function position.
*/ 
void luaD_call (lua_State *L, StkId func, int nResults) {
  // 函数调用的层级计数自增，并检查有没超过上限
  if (++L->nCcalls >= LUAI_MAXCCALLS) {
    if (L->nCcalls == LUAI_MAXCCALLS)
      // 层级溢出时，直接抛出异常
      luaG_runerror(L, "C stack overflow");
    else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
      // errfunc处理上面的调用层级溢出异常时，溢出得更离谱了，就调用该逻辑分支
      luaD_throw(L, LUA_ERRERR);  /* error while handing stack error */
  }

  // precall是执行一些准备工作，将接下来函数执行的环境布置好
  // 但如果func是C函数，那么在luaD_precall()内部就会被直接执行
  if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
    // 取出函数原型Proto中的指令一个个执行，初看的话，把lua函数的函数体内容想象为`return 1, 2, 3, 4`的简单形式，直接看OP_RETURN指令的操作即可
    luaV_execute(L, 1);  /* call it */

  // 函数调用已经进入结束阶段，调用层级回归
  L->nCcalls--;
  luaC_checkGC(L);
}


static void resume (lua_State *L, void *ud) {
  // 第一个实参所在的虚拟栈栈位
  StkId firstArg = cast(StkId, ud);
  CallInfo *ci = L->ci;
  // 初始执行协程
  if (L->status == 0) {  /* start coroutine? */
    lua_assert(ci == L->base_ci && firstArg > L->base);
    // 执行的返回值全部保留，非LClosure则直接退出
    if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
      return;
  }
  // 从上一次yield开始继续执行
  else {  /* resuming from previous yield */
    lua_assert(L->status == LUA_YIELD);
    L->status = 0;
    if (!f_isLua(ci)) {  /* `common' yield? */
      /* finish interrupted execution of `OP_CALL' */
      lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
                 GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
      
      // 将firstArg以及后续附带的多个参数，作为yield()的返回值，它们已经在虚拟栈上，yield()函数真正结束执行
      // 如果并不期待所有的返回值，那么则需要调整一下栈顶
      if (luaD_poscall(L, firstArg))  /* complete it... */
        L->top = L->ci->top;  /* and correct top if not multiple results */
    }
    else  /* yielded inside a hook: just continue its execution */
      L->base = L->ci->base;
  }

  // 继续执行LClosure中的后续指令集
  luaV_execute(L, cast_int(L->ci - L->base_ci));
}


static int resume_error (lua_State *L, const char *msg) {
  L->top = L->ci->base;
  setsvalue2s(L, L->top, luaS_new(L, msg));
  incr_top(L);
  lua_unlock(L);
  return LUA_ERRRUN;
}


LUA_API int lua_resume (lua_State *L, int nargs) {
  int status;
  lua_lock(L);
  if (L->status != LUA_YIELD) {
    if (L->status != 0)
      return resume_error(L, "cannot resume dead coroutine");
    else if (L->ci != L->base_ci)
      return resume_error(L, "cannot resume non-suspended coroutine");
  }
  // 预留的接口
  luai_userstateresume(L, nargs);
  // 为什么可以断言co->nCcalls == 0呢？因为在某些时机，例如抛出异常前的错误消息处理函数（C 或 Lua）的执行过程中，协程是不能被挂起的，注意分析co的状态。
  lua_assert(L->errfunc == 0 && L->nCcalls == 0);
  // 以保护模式执行resume函数
  status = luaD_rawrunprotected(L, resume, L->top - nargs);
  if (status != 0) {  /* error? */
    L->status = cast_byte(status);  /* mark thread as `dead' */
    // 根据error code，将errMsg生成或复制到栈顶
    luaD_seterrorobj(L, status, L->top);
    L->ci->top = L->top;
  }
  else
    // 函数被正常执行，那么沿用co->status的状态即可
    status = L->status;

  lua_unlock(L);
  return status;
}


LUA_API int lua_yield (lua_State *L, int nresults) {
  // 预留的编程接口
  luai_userstateyield(L, nresults);
  lua_lock(L);
  // 在某些时机，例如抛出异常前的错误消息处理函数（C 或 Lua）的执行过程中，协程是不能被挂起的，注意分析co的状态。
  if (L->nCcalls > 0)
    luaG_runerror(L, "attempt to yield across metamethod/C-call boundary");

  // 在调用yield()前，要返回给（初次）resume()调用处的实参，已经在虚拟栈中，返回到上一个函数层级后，按逻辑继续执行即可
  // co当中函数调用链的执行，暂时停止在这一个点上，本次lua vm的resume()执行完毕，这部分返回值会从co被复制到源lua_State的虚拟栈中，见auxresume()

  // 保护下方的虚拟栈槽位
  L->base = L->top - nresults;  /* protect stack slots below */
  L->status = LUA_YIELD;
  lua_unlock(L);
  return -1;
}


// old_top是本次函数调用的目标函数（TValue），到虚拟栈栈底的偏移量，即，原先的虚拟栈栈顶
// ef则是errMsg处理函数到虚拟栈栈底的偏移量，有可能为0
int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) {
  int status;
  unsigned short oldnCcalls = L->nCcalls; // C函数调用链的层数计数

  ptrdiff_t old_ci = saveci(L, L->ci); // L->ci 到 L->base_ci 的偏移量; 初始的lua_State, L->ci和L->base_ci其实指向同一个(第一个)CallInfo
  lu_byte old_allowhooks = L->allowhook; // 暂时不知道这个有啥用，应该是hook机制相关的

  // 将lua_State原先的错误消息处理函数缓存起来，本次函数执行用新指定的
  ptrdiff_t old_errfunc = L->errfunc; 

  // 接下来在luaD_rawrunprotected()的运行过程中，如果有异常发生，那么原生的errMsg会被推到虚拟栈栈顶，然后检查有无L->errfunc，
  // 有的话则将原生的errMsg会作为errfunc的实参，调用errfunc，errfunc通常会将经过处理的errStr留在虚拟栈栈顶
  // 具体见luaG_errormsg()，从语言机制的角度上来说，luaD_throw()之后，函数调用链的堆栈就被回滚删除了，所以一般是在errfunc中，去收集报错时的堆栈信息
  L->errfunc = ef;

  // 在保护模式下运行func指代的f_call()函数，struct CallS指针u，作为f_call()的实参
  status = luaD_rawrunprotected(L, func, u);
  if (status != 0) {  /* an error occurred? */
    StkId oldtop = restorestack(L, old_top);

    // luaF_close()看起来是对upvalues相关的处理
    luaF_close(L, oldtop);  /* close eventual pending closures */

    // 将errStr设置到old_top槽位，并调整L->top = old_top + 1，errStr可能是原生的errMsg，也可能是经过errfunc处理后生成的newErrMsg
    luaD_seterrorobj(L, status, oldtop);

    // 异常报错后，L的成员变量都恢复(回滚)到lua vm被驱动前的状态
    // 如果是初始lua_State，可以看下preinit_state()的内容
    L->nCcalls = oldnCcalls;
    L->ci = restoreci(L, old_ci);
    L->base = L->ci->base;
    L->savedpc = L->ci->savedpc;
    L->allowhook = old_allowhooks;

    // L的CallInfo数组，其大小如果大于LUAI_MAXCALLS，尝试将它的大小调整回去
    restore_stack_limit(L);
  }

  // func指代的函数调用结束，errfunc重置为旧的
  L->errfunc = old_errfunc;
  return status;
}



/*
** Execute a protected parser.
*/
struct SParser {  /* data to `f_parser' */
  ZIO *z;
  Mbuffer buff;  /* buffer to be used by the scanner */
  const char *name;
};

static void f_parser (lua_State *L, void *ud) {
  int i;
  Proto *tf;
  Closure *cl;
  struct SParser *p = cast(struct SParser *, ud);
  int c = luaZ_lookahead(p->z);
  luaC_checkGC(L);
  tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
                                                             &p->buff, p->name);
  cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
  cl->l.p = tf;
  for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */
    cl->l.upvals[i] = luaF_newupval(L);
  setclvalue(L, L->top, cl);
  incr_top(L);
}


int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) {
  struct SParser p;
  int status;
  p.z = z; p.name = name;
  luaZ_initbuffer(L, &p.buff);
  status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
  luaZ_freebuffer(L, &p.buff);
  return status;
}


